package mule.model;

//<editor-fold defaultstate="collapsed" desc="Imports">

import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
import java.util.HashMap;
import mule.model.enums.MapType;
import mule.model.enums.Difficulty;
import mule.model.enums.Race;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import mule.model.enums.GameStage;

//</editor-fold>

/**
 * Contains all of the back end information about the game.
 * 
 * @author stephen
 */
public class GameModel 
{
    // <editor-fold defaultstate="collapsed" desc="Properties">

    public static int MAP_COLS = 9;
    public static int MAP_ROWS = 5;

    public final ObservableList<Player> players = FXCollections.observableArrayList();
    public final ObservableList races           = FXCollections.observableArrayList();
    public final ObservableList difficulties    = FXCollections.observableArrayList();
    public final ObservableList mapTypes        = FXCollections.observableArrayList();
    public final ObservableList colors          = FXCollections.observableArrayList();

    public ObjectProperty<Player> turn      = new SimpleObjectProperty<>();
    public ObjectProperty<Tile>[][] tileMap = new ObjectProperty[MAP_ROWS][MAP_COLS];
    public ObjectProperty<Player>[][] map   = new ObjectProperty[MAP_ROWS][MAP_COLS];
    public HashMap<Integer, Integer> gambleValues = new HashMap();
    public HashMap<Integer, Integer> randomEventValues = new HashMap();
    int[] scores                            = new int[4];
    
    ObjectProperty<Difficulty> difficulty   = new SimpleObjectProperty();
    ObjectProperty<MapType> mapType         = new SimpleObjectProperty();
    ObjectProperty<GameStage> gameStage     = new SimpleObjectProperty();
    IntegerProperty numPlayers              = new SimpleIntegerProperty();
    IntegerProperty round                   = new SimpleIntegerProperty( 1 );
    private StringProperty informationText  = new SimpleStringProperty();

    private int randVal                     = 0; //num to decide if an event will happen
    private int eventVal                    = 0; //num to decide which event will happen
    private Random rand                     = new Random();

    // </editor-fold>


    // <editor-fold defaultstate="collapsed" desc="Singleton/Constructor">

    /**
     * Singleton constructor for the Game Model
     */
    private GameModel() 
    {
        for( int i = 0; i < MAP_ROWS; i++ )
        {
            for( int j = 0; j < MAP_COLS; j++ )
            {
                map[i][j] = new SimpleObjectProperty<>(null);
            }
        }

        for( Race race : Race.values() )
        {
            races.add( race );
        }

        for( Difficulty diff : Difficulty.values() )
        {
            difficulties.add( diff );
        }

        for( MapType type : MapType.values() )
        {
            mapTypes.add( type );
        }

        gambleValues.put( 1, 50 );
        gambleValues.put( 2, 50 );
        gambleValues.put( 3, 50 );
        gambleValues.put( 4, 100 );
        gambleValues.put( 5, 100 );
        gambleValues.put( 6, 100 );
        gambleValues.put( 7, 100 );
        gambleValues.put( 8, 150 );
        gambleValues.put( 9, 150 );
        gambleValues.put( 10, 150 );
        gambleValues.put( 11, 150 );
        gambleValues.put( 12, 200 );
        
        randomEventValues.put( 1, 25 );
        randomEventValues.put( 2, 25 );
        randomEventValues.put( 3, 25 );
        randomEventValues.put( 4, 50 );
        randomEventValues.put( 5, 50 );
        randomEventValues.put( 6, 50 );
        randomEventValues.put( 7, 50 );
        randomEventValues.put( 8, 75 );
        randomEventValues.put( 9, 75 );
        randomEventValues.put( 10, 75 );
        randomEventValues.put( 11, 75 );
        randomEventValues.put( 12, 100 );
        System.out.println("randoms in");
        informationText.set( "Nothing to report." );
        gameStage.set( GameStage.PropertySelection );
    }

    /**
     * Getter for our MuleModel Singleton.
     * 
     * @return an instance of the MuleModel
     */
    public static GameModel getInstance()
    {
        return MuleModelHolder.INSTANCE;
    }

    /**
     * Private class to implement a Singleton of our Mule Model.
     * 
     * #DESIGN_PRINCIPLE
     */
    private static class MuleModelHolder
    {
        private static final GameModel INSTANCE = new GameModel();    
    }   

    // </editor-fold>


    // <editor-fold defaultstate="collapsed" desc="Getters and Setters">

    /**
     * Returns an ObjectProperty representation of the game difficulty
     * 
     * @return the difficulty of the game as an ObjectProperty
     */
    public ObjectProperty difficultyProperty()
    {
        if( difficulty == null )
        {
            difficulty = new SimpleObjectProperty( Difficulty.values()[1], "Default" );
        }
        return difficulty;
    }

    /**
     * Returns the difficulty enumeration as a String
     * 
     * @return the current difficulty of the game as a String
     */
    public String getDifficulty()
    {
        return difficultyProperty().get().toString();
    }

    /**
     * Sets the difficulty using a String parameter of the enumeration
     * 
     * @param difficulty the difficulty to set the game to
     */
    public void setDifficultyWithString( String difficulty )
    {
        difficultyProperty().set( Difficulty.valueOf( difficulty ) );
    }

    /**
     * An alternate setter for difficulty that takes in a string and converts it.
     * 
     * @param difficulty 
     */
    public void setDifficulty( Difficulty difficulty )
    {
        difficultyProperty().set( difficulty );
    }

    /**
     * Returns the map type as an ObjectProperty
     * 
     * @return the map type of the current game as an ObjectProperty
     */
    public ObjectProperty<MapType> mapTypeProperty()
    {
        if( mapType == null )
        {
            mapType = new SimpleObjectProperty( MapType.values()[1], "Default" );
        }
        return mapType;
    }

    /**
     * Returns the current game map type as a String value
     * 
     * @return The map type of the game as a String
     */
    public String getMapType()
    {
        return mapTypeProperty().get().toString();
    }

    /**
     * Sets the mapType using a String parameter
     * 
     * @param mapType the mapType to set the game map type to
     */
    public void setMapTypeWithString( String mapType )
    {
        mapTypeProperty().set( MapType.valueOf( mapType ) );
    }

    /**
     * An alternate setter that takes in an actual enumeration instead of the
     * string representation.
     * 
     * @param mapType the map type to set
     */
    public void setMapType( MapType mapType )
    {
        mapTypeProperty().set( mapType );
    }

    /**
     * Returns the number of players as an IntegerProperty
     * 
     * @return the number of players in the game as an IntegerProperty
     */
    public IntegerProperty numPlayersProperty()
    {
        if( numPlayers == null )
        {
            numPlayers = new SimpleIntegerProperty( 0, "Default" );
        }
        return numPlayers;
    }

    /**
     * Returns the number of players as an integer  
     * @return the number of players in the game
     */
    public int getNumPlayers()
    {
        return numPlayersProperty().get();
    }

    /**
     * Sets the number of players in the game through an integer value
     * 
     * @param numPlayers  the number of players to set in the game
     */
    public void setNumPlayers( int numPlayers )
    {
        numPlayersProperty().set( numPlayers );
    }

    /**
     * Gets the next player in the Array List of players to increment turns
     * 
     * @return the next Player in the array list 
     */
    public Player getNextPlayer()
    {
        for( int i = 0; i < getNumPlayers(); i++ )
        {
            if( players.get(i) == turn.get() )
            {
                if( i == players.size() - 1 )
                {
                    return players.get(0);
                }
                return players.get( i + 1 );
            }
        }


        return null;
    }

    /**
     * Used to add Players to the game on initialization
     * 
     * @param player the Player to add
     */
    public void addPlayerToArray( Player player )
    {
        players.add( player );
    }

    /**
     * Returns an ObjectProperty representation of the turn
     * 
     * @return the turn as an ObjectProperty 
     */
    public ObjectProperty<Player> turnProperty()
    {
        return turn;
    }

    /**
     * Returns the Player whose turn it is
     * 
     * @return the current player 
     */
    public Player getTurn()
    {
        return turnProperty().get();
    }

    /**
     * Sets the turn to a new player
     * 
     * @param player the player to have a turn
     */
    public void setTurn( Player player )
    {
        turnProperty().set( player );
        if (getGameStage() == GameStage.PropertySelection)
            randomEvent();
    }

    /**
     * Returns a StringProperty representation of the information text for
     * the game
     * 
     * @return Information text of the game as a StringProperty
     */
    public StringProperty informationTextProperty()
    {
        if( informationText == null )
        {
            informationText = new SimpleStringProperty( "", "Default" );
        }

        return informationText;
    }

    /**
     * Returns information text from the game as a String
     * 
     * @return information text from the game
     */
    public String getInformationText()
    {
        return informationTextProperty().get();
    }
    
    /**
     * Set the game information text by passing in a String
     * 
     * @param text The text to put into the information text 
     */
    public void setInformationText( String text )
    {
        informationTextProperty().set( text );
    }

    /**
     * Returns a StringProperty representation of the stage status text
     * 
     * @return the stage status text
     */
    public ObjectProperty<GameStage> gameStageProperty()
    {
        if( gameStage == null )
        {
            gameStage = new SimpleObjectProperty( GameStage.PropertySelection, "Default" );
        }

        return gameStage;
    }

    public GameStage getNextGameStage()
    {
        if( gameStageProperty().get().equals( GameStage.PropertySelection) )
        {
            return GameStage.Developement;
        }
        else if( gameStageProperty().get().equals( GameStage.Developement ) )
        {
            return GameStage.Store;
        }
        else 
        {
            return GameStage.PropertySelection;
        }
    }

    /**
     * Returns a String representation of the stage status text.
     * 
     * @return the stage status text
     */
    public GameStage getGameStage()
    {
        return gameStageProperty().get();
    }

    /**
     * Sets the stage status text
     * 
     * @param text the text to set
     */
    public void setGameStage( GameStage stage )
    {
        if( stage.equals( GameStage.PropertySelection ) )
        {
            setRound( getRound() + 1 );
            if( getRound() == 13 )
            {
                end();
            }
            else
            {
                Collections.sort( players, new PlayerComparator() );
                spoilFood();
            }
        }
        gameStageProperty().set( stage );
    }

    /**
     * Returns the property instance of the tileMap
     * 
     * @return the tileMap as an Object property
     */
    public ObjectProperty<Tile>[][] tileMapProperty()
    {
        return tileMap;
    }

    /**
     * Returns the round as an integer property
     * 
     * @return the current round of the game as an integer property
     */
    public IntegerProperty roundProperty()
    {
        if( round == null )
        {
            round = new SimpleIntegerProperty( 1, "Default" );
        }

        return round;
    }

    /**
     * Returns the round of the game as a primitive integer
     * 
     * @return the round of the game as an integer
     */
    public int getRound()
    {
        return roundProperty().get();
    }

    /**
     * Sets the round of the game using an integer parameter
     * 
     * @param round the round to set the game to
     */
    public void setRound( int round )
    {
        roundProperty().set( round );
    }
    
    public int getEventVal(){
        return this.eventVal;
    }
    
    public int getRandVal(){
        return this.randVal;
    }
    
    
    // </editor-fold>
   

    // <editor-fold defaultstate="collapsed" desc="Random Events">
    /**
     * Calculates whether or not a random event occurs to the current player,
     * and applies the changes.
     * 
     */
    public void randomEvent() {
        randVal = rand.nextInt(100) + 1;
        int minScore = 5000;
        for ( int i = 1; i < players.size(); i++ )
        {
            if ( players.get(i).getMoney() < minScore )
            {
                minScore = players.get(i).getMoney();
            }
        }
        if (getTurn().getMoney() <= minScore)
        {
            eventVal = rand.nextInt(4) + 1;
        }
        else if (getTurn().getMoney() > minScore) 
        {
            eventVal = rand.nextInt(7) + 1;
        }

        System.out.println(eventVal + " " + randVal);
        if (randVal <= 27)
        {
            System.out.println("random occurs");
            if (eventVal == 1 )
            {// event 1
                informationText.set(informationText.get().concat(
                    "\nYOU JUST RECEIVED A PACKAGE FROM THE GT ALUMNI CONTAINING 3 FOOD AND 2 ENERGY UNITS."));                  
                getTurn().setFood(getTurn().getFood() + 3 );
                getTurn().setEnergy(getTurn().getEnergy() + 2 );
                            System.out.println("random 1");
            }
            else if (eventVal == 2 )
            { //event 2
                informationText.set(informationText.get().concat(
                    "\nA WANDERING TECH STUDENT REPAID YOUR HOSPITALITY BY LEAVING TWO BARS OF ORE."));
                getTurn().setOre(getTurn().getOre() + 2 );
                            System.out.println("random 2");
            }
            else if (eventVal == 3 )
            { //event 3
                informationText.set(informationText.get().concat(
                    "\nTHE MUSEUM BOUGHT YOUR ANTIQUE PERSONAL COMPUTER FOR $ " + 8*randomEventValues.get(getRound()) + "."));
                getTurn().setMoney(getTurn().getMoney() + 8 * randomEventValues.get(getRound()) );
                        System.out.println("random 3");
            }
            else if (eventVal == 4 ) 
            { //event 4
                informationText.set(informationText.get().concat(
                    "\nYOU FOUND A DEAD MOOSE RAT AND SOLD THE HIDE FOR $ " + 2*randomEventValues.get(getRound()) + "."));    
                getTurn().setMoney(getTurn().getMoney() + 2 * randomEventValues.get(getRound()) );
                        System.out.println("random 4");
            }
            else if (eventVal == 5 )
            { //event 5
                informationText.set(informationText.get().concat(
                    "\nFLYING CAT-BUGS ATE THE ROOF OFF YOUR HOUSE. REPAIRS COST $ " + 4*randomEventValues.get(getRound()) + "."));      
                getTurn().setMoney(getTurn().getMoney() - 4 * randomEventValues.get(getRound()) );
                        System.out.println("random 5");
            }
            else if (eventVal == 6 )
            { //event 6
                informationText.set(informationText.get().concat(
                    "\nMISCHIEVOUS UGA STUDENTS BROKE INTO YOUR STORAGE SHED AND STOLE HALF YOUR FOOD."));
                getTurn().setFood(getTurn().getFood() / 2 );
                
                        System.out.println("random 6");
            }
            else if (eventVal == 7 )
            { //event 7
                informationText.set(informationText.get().concat(
                    "\nYOUR SPACE GYPSY INLAWS MADE A MESS OF THE TOWN. IT COST YOU " + 4*randomEventValues.get(getRound()) + " TO CLEAN IT UP."));
                getTurn().setMoney(getTurn().getMoney() - 6 * randomEventValues.get(getRound()) );
                        System.out.println("random 7");
            }
        }      
    }
    // </editor-fold>

    
    // <editor-fold defaultstate="collapsed" desc="Helper Methods">

    /**
     * Gets rid of anywhere from 1 - 20 % of the player's food each round from
     * spoilage.
     */
    public void spoilFood()
    {
        for( Player player : players )
        {
            double percentToLose = rand.nextInt( 19 ) + 1;
            double amountToLose = ( player.getFood() * percentToLose / 100 );

            if( amountToLose > player.getFood() )
            {
                player.setFood( 0 );
            }
            else
            {
                player.setFood( player.getFood() - (int) amountToLose );
            }

            informationText.get().concat( "Player " + player.getName() + " lost " + amountToLose + " food because it spoiled!" );
        }
    }

    /**
     * Creates the standard map configuration for right now although this could
     * support many different types of maps in the future.
     * 
     * @param mapType The type of map to create
     */
    public void initializeMap( MapType mapType )
    {
        if(mapType.compareTo(MapType.RANDOM) != 0)
        {
            tileMap[0][0] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[0][1] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[0][2] = new SimpleObjectProperty( new MountainOneTile() );
            tileMap[0][3] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[0][4] = new SimpleObjectProperty( new RiverTile() );
            tileMap[0][5] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[0][6] = new SimpleObjectProperty( new MountainThreeTile() );
            tileMap[0][7] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[0][8] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[1][0] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[1][1] = new SimpleObjectProperty( new MountainOneTile() );
            tileMap[1][2] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[1][3] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[1][4] = new SimpleObjectProperty( new RiverTile() );
            tileMap[1][5] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[1][6] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[1][7] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[1][8] = new SimpleObjectProperty( new MountainThreeTile() );
            tileMap[2][0] = new SimpleObjectProperty( new MountainThreeTile() );
            tileMap[2][1] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[2][2] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[2][3] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[2][4] = new SimpleObjectProperty( new TownTile() );
            tileMap[2][5] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[2][6] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[2][7] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[2][8] = new SimpleObjectProperty( new MountainOneTile() );
            tileMap[3][0] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[3][1] = new SimpleObjectProperty( new MountainTwoTile() );
            tileMap[3][2] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[3][3] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[3][4] = new SimpleObjectProperty( new RiverTile() );
            tileMap[3][5] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[3][6] = new SimpleObjectProperty( new MountainTwoTile() );
            tileMap[3][7] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[3][8] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[4][0] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[4][1] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[4][2] = new SimpleObjectProperty( new MountainTwoTile() );
            tileMap[4][3] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[4][4] = new SimpleObjectProperty( new RiverTile() );
            tileMap[4][5] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[4][6] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[4][7] = new SimpleObjectProperty( new PlainsTile() );
            tileMap[4][8] = new SimpleObjectProperty( new MountainTwoTile() );
        }
        else
        {
            Random rand = new Random();
            int riverPos = rand.nextInt(9);
            /* Create 5 rows of tiles */
            for(int i = 0; i<5; i++)
            {
                /* From the first column until the river column */
                for(int j = 0; j<9; j++)
                {
                    if(j != riverPos)
                    {
                        /* Select a random int from [0, 7]*/
                        int type = rand.nextInt( 8 );
                        /* Give plains tiles a 5/8 chance so that the map isn't too mountainous */
                        if(type < 5)
                        {
                            tileMap[i][j] = new SimpleObjectProperty ( new PlainsTile() );
                        }
                        else if (type == 5)
                        {
                            tileMap[i][j] = new SimpleObjectProperty( new MountainOneTile() );
                        }
                        else if (type == 6)
                        {
                            tileMap[i][j] = new SimpleObjectProperty( new MountainTwoTile() );
                        }
                        else if (type == 7)
                        {
                            tileMap[i][j] = new SimpleObjectProperty( new MountainThreeTile() );
                        }
                    }
                    else
                    {
                        if(i != 2)
                        {
                            /* Assign the river tile */
                            tileMap[i][riverPos] = new SimpleObjectProperty( new RiverTile() );
                        }
                        else
                        {
                            /* Assign the town tile */
                            tileMap[2][riverPos] = new SimpleObjectProperty( new TownTile() );
                        }
                    }
                }
            }
        }
    }

    // </editor-fold>

    private void end() {
        System.exit( 1 );
    }

    static class PlayerComparator implements Comparator<Player>
    {
        @Override
        public int compare( Player one, Player two )
        {
            return one.getMoney() - two.getMoney();
        }
    }
}